from
howtographql.com
API需求:
Retrieve a list
(feed) of link elements- Allow users to
signup up
with their name, email and password - Users who signed up should be able to
login
again with their email and password - Allow authenticated users to
post
new link elements - Allow authenticated users to
upvote
an existing link element - Send
realtime updates
to subscribed clients when a new link element is created - Send
realtime updates
to subscribed clients when an existing link element is upvoted
定义 应用 schema
这一步非常好,根据应用的需求首先定义需要实现的 query,mutation,subscribe,然后围绕这个schema 展开工作
,目前我们不关注User
,Link
,Vote
的实现细节
1 | type Query { |
接下来就是逐渐实现这些操作.每一步实现的流程实际是基本类似的-这也就是schema-driven-development
- 调整 data model
- 调整之后,部署服务
- 用新的根字段扩展应用的 schema
- 实现 resolver, 通过代理执行对应的 Prisma resolver
初始化文件结构
1 | . |
src
包含应用的实现代码和应用schema(application schema)
,Prisma根据 dataModel生成的Prisma schema
database
包含初始化配置文件 prisma.yml和应用的 data model
src/schema.graphql(Apllication schema)
定义了暴露给 client 端的 graphql API.
src/generated/prisma.graphql(Prisma schema)
定义了对数据库操作的 CRUD API. 对于在 data model
中的每个类型, Prisma会生成读写数据库节点的操作
data model
并不是实际的 Graphql schema,缺少 root type. data model 用于生成实际执行的 schema.
理解初始化步骤
有两个依赖包:
graphql-yoga
: GraphQL Server 需要的文件,实际是 express 服务器prisma-binding
: 可以允许绑定应用 schema 的 resolvers到自动生成的 Prisma database服务.
1 | const server = new GraphQLServer({ |
查询
在 data model 中定义 Link
type
1 | type Link { |
yarn prisma deploy 之后
会自动生成prisma.graphql
1 | type Query { |
对 应用的 schema 做出调整
定义feed
查询
1 | # import Link from "./generated/prisma.graphql" |
实现 feed
的 resolver
每个Query
,Mutation
都由 resolver 支撑. 相应的定义为 Query.js
,Mutation.js
,Subscription.js
.
1 | function feed(parent, args, context, info) { |
resolver 函数接收四个参数,
parent
:包含 resolver 链的初始值args
:包含查询参数的对象.context
:包含定制的数据,可以在 resolver 链中传递.例如每个 resolver 都可以读写info
包含了 AST信息
打开 index.js
替换下面的resolver
对象:
1 | const resolvers = { |
测试 API
在项目的根目录执行
1 | yarn dev |
打开 graphiQL, 输入
1 | mutation { |
Mutations
Mutation for posting new links
打开 src/schema.graphql
添加如下代码
1 | type Mutation { |
在src/resolvers
创建新的Mutation.js
,添加代码:
1 | function post(parent, args, context, info) { |
在index.js
中添加新的mutation
声明
1 | const Mutation = require('./resolvers/Mutation') |
测试一下
1 | mutation { |
Signup&Login
为用户提供登录功能
Signup
注册的实现步骤:
- 服务器收到新用户的
email
,password
(还有name
) - 服务器在数据库创建新用户,并储存
name
,email
和 hash过的密码 - 服务器创建一个认证 token(JWT)
- 服务器把 token返回给用户
Login
- 服务器收到
login
mutation - 服务器比较
password
的一致性 - 如果密码一致, 服务器创建一个 token
- 服务器把 token 返回给用户
实现注册流程
创建secret
1 | const APP_SECRET = 'GraphQL-is-aw3some' |
在应用的 schema 中添加 signup
Mutation
1 | type Mutation { |
src/schema.graphql
1 | type AuthPayload { |
database/datamodel.graphql
1 | type User { |
部署以后会生成
1 | type Query { |
打开应用 schema src/schema.graphql
添加如下的类型
1 | type User{ |
这里的定义和 Prisma schema 里的定义类似,但是没有password
,也就是不允许用户查询 password
接下来需要signup
mutation
1 | async function signup(parent, args, context, info) { |
在signup
resolver 中, 首先创建密码的加密版本, 接下来, 使用Prisma
的实例,创建新的User
节点.最后返回包含AuthPayload
包含创建的 token 和新的 user 对象
实现login
mutation
接下来,实现login
mutation.
src/schema.graphql
中添加Mutation 类型
1 | type Mutation { |
打开 src/resolvers/Mutation.js
添加如下函数
1 | async function login(parent, args, context, info) { |
首先使用 email 查询是否存在, 然后比较密码是否一致. 完成后,返回一个 token 给用户
应用 Authentication
创建User
和Link
的关联
在需求中,我们提出只有通过认证的用户才可以创建新的 Link
元素.
打开 database/datamodel.js
改变User
和Link
类型
1 | type Link { |
可以执行如下的操作
1 | mutation { |
打开 src/resolvers/Mutation.js
改变post
1 | function post(parent, { url, description }, context, info) { |
和之前的版本不同的地方是,首先获取到用户的 id, 然后传递给createLink
-mutation 作为connect
的参数.
现在我们需要一个工具函数 getUserId
,
src/utils.js
1 | function getUserId(context) { |
context
参数有一个request
属性,代表着携带 query 或者 mutation的 HTTP 请求. 重要的是,它提供了访问头 .token就在头部中
认证一个用户
- 在 playground中, 发送
signup
或者login
mutation,从 graphql server 中获取到token
. - 在
PlayGround
中设置Header - 发送
post
mutation,创建新的Link
元素
1 | mutation { |
设置好以后,执行
1 | mutation { |
投票部分
Subscriptions
实现 GraphQL订阅
打开src/schema.graphql
,添加Subscription
1 | type Subscription { |
在src/resolvers
创建一个新的文件, SubScription.js
添加如下代码:
1 | const newLink = { |
现在的src/inde.js
文件
1 | const { GraphQLServer } = require('graphql-yoga') |